package com.utils;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @author sad
 */
public class XPathEngine {

    private Document doc;
    private XPath xpathObj;
    private Map<String, XPathExpression> expressions = new HashMap<String, XPathExpression>();

    public XPathEngine(String xml) {
        byte[] bytes;
        try {
            bytes = xml.getBytes("UTF-8");
            ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
            init(stream);
            stream.close();
        } catch (IOException ex) {
            Logger.getLogger(XPathEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public XPathEngine(InputStream stream) {
        init(stream);
    }

    public XPathEngine(File file) {
        try {
            FileInputStream stream = new FileInputStream(file);
            init(new BufferedInputStream(stream));
            stream.close();
        } catch (IOException ex) {
            Logger.getLogger(XPathEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private XPathExpression getExpression(String xpath) throws XPathExpressionException {
        if (!expressions.containsKey(xpath)) {
            XPathExpression expression = xpathObj.compile(xpath);
            
            expressions.put(xpath, expression);
        }
        return expressions.get(xpath);
    }

    private void init(InputStream stream) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            doc = (Document) builder.parse(stream);
            XPathFactory xPathfactory = XPathFactory.newInstance();
            xpathObj = (XPath) xPathfactory.newXPath();
            xpathObj.setNamespaceContext(new UniversalNamespaceResolver(doc));
        } catch (Exception ex) {
            Logger.getLogger(XPathEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public String getTextValueByXPath(String xpath, Object parent) {
        try {
            XPathExpression expression = getExpression(xpath);
            return (String) expression.evaluate(parent, XPathConstants.STRING);
        } catch (XPathExpressionException ex) {
            Logger.getLogger(XPathEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    public String getTextValueByXPath(String xpath) {
        return getTextValueByXPath(xpath, doc);
    }

    public Element getNodeByXPath(String xpath, Object parent) {
        try {
            XPathExpression expression = getExpression(xpath);
            return (Element) (Node) expression.evaluate(parent, XPathConstants.NODE);
        } catch (XPathExpressionException ex) {
            Logger.getLogger(XPathEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    public Element getNodeByXPath(String xpath) {
        return getNodeByXPath(xpath, doc);
    }

    public NodeList getNodeSetByXPath(String xpath, Object parent) {
        try {
            XPathExpression expression = getExpression(xpath);
            return (NodeList) expression.evaluate(parent, XPathConstants.NODESET);
        } catch (XPathExpressionException ex) {
            Logger.getLogger(XPathEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    public NodeList getNodeSetByXPath(String xpath) {
        return getNodeSetByXPath(xpath, doc);
    }

    public class UniversalNamespaceResolver implements NamespaceContext {
        // the delegate
        private Document sourceDocument;
        /**
         * This constructor stores the source document to search the namespaces in
         * it.
         * 
         * @param document
         *            source document
         */
        public UniversalNamespaceResolver(Document document) {
            sourceDocument = document;
        }

        /**
         * The lookup for the namespace uris is delegated to the stored document.
         * 
         * @param prefix
         *            to search for
         * @return uri
         */
        @Override
        public String getNamespaceURI(String prefix) {
            if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
                return sourceDocument.lookupNamespaceURI(null);
            } else {
                return sourceDocument.lookupNamespaceURI(prefix);
            }
        }

        /**
         * This method is not needed in this context, but can be implemented in a
         * similar way.
         */
        @Override
        public String getPrefix(String namespaceURI) {
            return sourceDocument.lookupPrefix(namespaceURI);
        }

        @Override
        public Iterator getPrefixes(String namespaceURI) {
            // not implemented yet
            return null;
        }

    }
}
